// Stipulation.cs
//
// Copyright (c) 2007 Mike Strobel
//
// This source code is subject to the terms of the Microsoft Reciprocal License (Ms-RL).
// For details, see <http://www.opensource.org/licenses/ms-rl.html>.
//
// All other rights reserved.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

using Supremacy.Game;
using Supremacy.Resources;

using Wintellect.PowerCollections;

namespace Supremacy.Diplomacy
{
    [Serializable]
    public abstract class Stipulation
    {
        #region Constants
        public const int DurationStepSize = 50;
        public const int InvalidDuration = 0;
        public const int ImmediateDuration = 1;
        public const int IndefiniteDuration = 255;
        public const int NotEnded = -1;
        public const int NotStarted = -1;
        #endregion

        #region Fields
        public static readonly int[] AllowedDurations = { ImmediateDuration, 50, 100, 150, 200, IndefiniteDuration };
        private static readonly List<Stipulation> _allStipulations;
        private StipulationEffect _effects;
        private byte _duration;
        private object _parameter;
        private StipulationStatus _status;
        private int _turnEnded;
        private int _turnStarted;
        #endregion

        public static ReadOnlyCollection<Stipulation> AllStipulations
        {
            get { return new ReadOnlyCollection<Stipulation>(_allStipulations); }
        }

        public static void Register(Stipulation stipulation)
        {
            if (stipulation == null)
                throw new ArgumentException("stipulation");
            bool exists = Algorithms.Exists(
                _allStipulations,
                delegate(Stipulation s)
                {
                     return s.GetType() == stipulation.GetType();
                });
            if (!exists)
            {
                stipulation = Activator.CreateInstance(stipulation.GetType()) as Stipulation;
                if (stipulation != null)
                {
                    _allStipulations.Add(stipulation);
                }
            }
        }

        #region Constructors
        static Stipulation()
        {
            _allStipulations = new List<Stipulation>();
            Register(new NonAggressionPactStipulation());
            Register(new TradeAgreementStipulation());
            Register(new OpenBordersStipulation());
            Register(new AllianceStipulation());
            Register(new MembershipStipulation());
            Register(new GiftStipulation());
            Register(new TerminateAgreementsStipulation());
            Register(new DeclareWarStipulation());
        }

        public Stipulation()
        {
            _effects = StipulationEffect.None;
            _status = StipulationStatus.Pending;
            _duration = ImmediateDuration;
            _turnStarted = NotStarted;
            _turnEnded = NotEnded;
        }
        #endregion

        public bool HasEffect(StipulationEffect effect)
        {
            return ((_effects & effect) == effect);
        }

        #region Properties
        public TurnNumber TurnStarted
        {
            get { return _turnStarted; }
            internal set { _turnStarted = value; }
        }

        public TurnNumber TurnEnded
        {
            get { return _turnEnded; }
        }

        public StipulationStatus Status
        {
            get { return this._status; }
        }

        public abstract string ShortDescription { get; }

        public virtual CivString LongDescription
        {
            get { return new CivString(CivString.DiplomacyCategory, this.ShortDescription); }
        }

        public bool HasEffects
        {
            get { return (_effects != StipulationEffect.None); }
        }

        public StipulationEffect Effects
        {
            get { return _effects; }
            protected set { _effects = value; }
        }

        public bool HasCustomDuration
        {
            get { return ((_duration != IndefiniteDuration) && (_duration != ImmediateDuration)); }
        }

        public bool IsRepeating
        {
            get { return (_duration > 1); }
        }

        public bool IsTerminated
        {
            get { return ((_status != StipulationStatus.Pending) && (_status != StipulationStatus.InEffect)); }
        }

        public bool IsCancelled
        {
            get
            {
                return ((_status == StipulationStatus.CancelledBySender)
                        || (_status == StipulationStatus.CancelledByRecipient));
            }
        }

        public bool IsInEffect
        {
            get { return (_status == StipulationStatus.InEffect); }
        }

        public virtual int MinDuration
        {
            get { return ImmediateDuration; }
        }

        public int Duration
        {
            get { return _duration; }
            set
            {
                if (IsDurationFixed)
                {
                    throw new InvalidOperationException(
                        "Duration cannot be set for this Stipulation.");
                }
                this.DurationInternal = value;
            }
        }

        protected internal int DurationInternal
        {
            get { return _duration; }
            set
            {
                if (value > IndefiniteDuration)
                {
                    value = IndefiniteDuration;
                }
                else if (value < MinDuration)
                {
                    value = MinDuration;
                }
                _duration = (byte)value;
            }
        }

        protected virtual bool AllowDuplicates
        {
            get { return true; }
        }

        public virtual object Parameter
        {
            get { return _parameter; }
            set { _parameter = value; }
        }

        public virtual bool IsParameterValid
        {
            get { return (!HasParameter || (Parameter != null)); }
        }

        public virtual bool IsDurationFixed
        {
            get { return false; }
        }

        public virtual bool IsMutual
        {
            get { return false; }
        }

        public bool HasParameter
        {
            get { return (ParameterType != null); }
        }

        public virtual Type ParameterType
        {
            get { return null; }
        }

        public bool HasDurationElapsed
        {
            get
            {
                return (Duration == IndefiniteDuration)
                    ? false
                    : ((GameContext.Current.TurnNumber - TurnStarted) > Duration);
            }
        }

        public bool IsPending
        {
            get { return (Status == StipulationStatus.Pending); }
        }
        #endregion

        #region Methods
        public void Apply(DiplomacyContext context)
        {
            if (context == null)
                return;
            if (_status == StipulationStatus.Pending)
            {
                ApplyOverride(context);
                _status = StipulationStatus.InEffect;
            }
        }

        public bool CanExecute(DiplomacyContext context)
        {
            if (context == null)
                return false;
            if (_status == StipulationStatus.InEffect)
            {
                return (!HasDurationElapsed
                        && (!HasParameter || (Parameter != null))
                        && CanExecuteOverride(context));
            }
            return false;
        }

        public void Execute(DiplomacyContext context)
        {
            if (context == null)
                return;
            if (_status == StipulationStatus.InEffect)
            {
                ExecuteOverride(context);
                if (_duration == ImmediateDuration)
                {
                    _turnEnded = GameContext.Current.TurnNumber;
                    _status = StipulationStatus.Expired;
                }
            }
        }

        public IList GetParameterList(DiplomacyContext context)
        {
            if (!HasParameter && (context == null))
                return new ArrayList(0);
            return GetParameterListOverride(context) ?? new ArrayList(0);
        }

        public bool IsSupersetOf(Stipulation otherStipulation)
        {
            if (otherStipulation == null)
                throw new ArgumentNullException("otherStipulation");
            if (ReferenceEquals(this, otherStipulation))
                return true;
            //if (otherStipulation.GetType().IsAssignableFrom(this.GetType()))
            //    return true;
            return IsSupersetOfOverride(otherStipulation);
        }

        public void Terminate(TerminationContext context)
        {
            if (context == null)
                return;
            if (_status == StipulationStatus.InEffect)
            {
                _turnEnded = GameContext.Current.TurnNumber;
                TerminateOverride(context);
                switch (context.Reason)
                {
                    case TerminationReason.Expired:
                        _status = StipulationStatus.Expired;
                        break;
                    case TerminationReason.CancelledBySender:
                        _status = StipulationStatus.CancelledBySender;
                        break;
                    case TerminationReason.CancelledByRecipient:
                        _status = StipulationStatus.CancelledByRecipient;
                        break;
                    case TerminationReason.Failed:
                        _status = StipulationStatus.Failed;
                        break;
                }
            }
        }

        protected virtual void ApplyOverride(DiplomacyContext context) {}

        protected virtual bool CanExecuteOverride(DiplomacyContext context)
        {
            StipulationEffect existingEffects = StipulationEffect.None;
            Relationship relationship = context.IsDemand
                ? context.OtherPartyRelationship
                : context.InitiatingPartyRelationship;
            foreach (Agreement otherAgreement in relationship.ActiveAgreements)
            {
                IList<Stipulation> stipulations = context.IsDemand
                    ? otherAgreement.Proposal.DemandStipulations
                    : otherAgreement.Proposal.OfferStipulations;
                foreach (Stipulation stipulation in stipulations)
                {
                    if ((stipulation != this)
                        && stipulation.IsInEffect
                        && (stipulation.TurnStarted > this.TurnStarted))
                    {
                        foreach (StipulationEffect effect in Enum.GetValues(typeof(StipulationEffect)))
                        {
                            if (stipulation.HasEffect(effect))
                                existingEffects |= effect;
                        }
                    }
                }
            }
            return ((this.Effects & ~existingEffects) != 0);
        }

        protected virtual void ExecuteOverride(DiplomacyContext context) {}

        protected virtual IList GetParameterListOverride(DiplomacyContext context)
        {
            return null;
        }

        protected virtual bool IsSupersetOfOverride(Stipulation otherStipulation)
        {
            StipulationEffect otherEffects = (StipulationEffect)((int)otherStipulation.Effects & ~((int)StipulationEffect.CloseBorders));
            return (((this.IsRepeating || !otherStipulation.IsRepeating)
                    || ((this is TreatyStipulation) && (otherStipulation is TreatyStipulation)))
                && ((this.Effects & otherEffects) == otherEffects));
        }

        protected virtual void TerminateOverride(TerminationContext context) {}

        public string GetDurationString()
        {
            if (this.Duration == ImmediateDuration)
            {
                return ResourceManager.GetString("STIPULATION_DURATION_IMMEDIATE");
            }
            else if (this.Duration == IndefiniteDuration)
            {
                return ResourceManager.GetString("STIPULATION_DURATION_INDEFINITE");
            }
            else
            {
                return ResourceManager.GetString("STIPULATION_DURATION_REPEATING");
            }
        }
        #endregion
    }

    public enum StipulationStatus
    {
        Pending,
        InEffect,
        Expired,
        CancelledBySender,
        CancelledByRecipient,
        Failed
    }

    [Flags]
    public enum StipulationEffect : byte
    {
        None = 0x00,
        CloseBorders = 0x01,
        GrantShipPassage = 0x02,
        AllowTrade = 0x04,
        ShareIntel = 0x08,
        ShareSupplies = 0x10,
        GrantRepairRights = 0x20,
        DefensiveAidRequired = 0x40,
        MembershipGranted = 0x80
    }
}